package cn.iocoder.yudao.module.vos.job.component;//package cn.iocoder.yudao.module.vos.job;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;

@Component
@Slf4j
public class AiNlpClient {

    @Value("${spring.ai.openai.base-url}")
    private String openaiBaseUrl;

    @Value("${spring.ai.openai.api-key}")
    private String apiKey;

    /**
     * 单文件转写：入参为本地 mp3 路径；返回转写文本
     */
    public String transcribeAudioText(String audioPath) {
        File mp3 = new File(audioPath);
        if (!mp3.isFile()) {
            throw new IllegalArgumentException("音频文件不存在: " + audioPath);
        }
        HttpResponse response = HttpRequest.post(openaiBaseUrl + "/audio/transcriptions")
                .header("Authorization", "Bearer " + apiKey)
                .form("file", mp3)
                .form("model", "whisper-1")
                .timeout(180_000)
                .execute();

        if (response.getStatus() != 200) {
            String body = response.body();
            log.error("转写失败: status={} body={}", response.getStatus(), body);
            throw new IllegalStateException("转写失败: " + response.getStatus());
        }
        JSONObject json = JSONUtil.parseObj(response.body());
        return json.getStr("text", "");
    }

    /**
     * 单段文本抽取：入参为一段转写文本；返回“严格 JSON 字符串”
     * 关键点：
     * - 不传 temperature（gpt-5-mini 会报 400）
     * - 使用 JSON 模式：response_format = {"type":"json_object"}
     * - 自动剥离可能的 Markdown 代码围栏
     */
    public String processTxtWithGpt(String transcriptText) {
        String prompt = buildExtractionPrompt(transcriptText);

        // messages
        JSONObject messageUser = JSONUtil.createObj()
                .put("role", "user")
                .put("content", prompt);

        // Chat Completions 请求体（注意：不传 temperature）
        JSONObject body = JSONUtil.createObj()
                .put("model", "gpt-5-mini")
                .put("messages", JSONUtil.createArray().put(messageUser))
                // JSON 模式：确保返回是合法 JSON（官方建议；生产可进一步用 json_schema）
                .put("response_format", JSONUtil.createObj().put("type", "json_object"))
                // 可选：限制输出长度（按需）
                // .put("max_tokens", 2048)
                ;

        HttpResponse resp = HttpRequest.post(openaiBaseUrl + "/chat/completions")
                .header("Authorization", "Bearer " + apiKey)
                .header("Content-Type", "application/json")
                .body(body.toString())
                .timeout(480_000)
                .execute();

        if (resp.getStatus() != 200) {
            String bodyStr = resp.body();
            log.error("GPT 抽取失败: status={} body={}", resp.getStatus(), bodyStr);
            // 针对 temperature 的 400（如果后续有人误加参数），做一次提示
            if (resp.getStatus() == 400 && bodyStr != null && bodyStr.contains("'temperature'")) {
                throw new IllegalStateException("GPT 抽取失败：该模型不支持 temperature 参数，请移除后重试（gpt-5-mini 固定为默认值 1）。");
            }
            throw new IllegalStateException("GPT 抽取失败: " + resp.getStatus());
        }

        JSONObject respJson = JSONUtil.parseObj(resp.body());
        String reply = respJson.getJSONArray("choices")
                .getJSONObject(0)
                .getJSONObject("message")
                .getStr("content");

        // 保险起见，去掉可能的 Markdown 包裹
        if (reply != null) {
            reply = reply.trim();
            if (reply.startsWith("```")) {
                reply = reply
                        .replaceFirst("^```(?:json)?\\s*", "")
                        .replaceFirst("\\s*```$", "")
                        .trim();
            }
        }
        return reply;
    }


    private String buildExtractionPrompt(String content) {
        StringBuilder sb = new StringBuilder(4096);
        sb.append("Please analyze the following phone call transcription (it may mix Filipino/English/Chinese). ")
                .append("Identify whether it is a marketing or collection scenario, and strictly output JSON according to the schema below ")
                .append("(OUTPUT JSON ONLY — no explanations/Markdown).\n")
                .append("{\n")
                .append("  \"company_name\": \"\",              // Company/App/Institution name; empty string if unknown\n")
                .append("  \"client_name\": \"\",               // Customer/Borrower name; empty string if unknown\n")
                .append("  \"callee_type\": \"\",               // Role of callee: \"borrower\" | \"ec\" | \"third_party\"; empty if unknown\n")
                .append("  \"relationship\": \"\",              // Relationship to borrower (mother/friend/colleague, etc.); empty if unknown\n")
                .append("  \"type\": \"\",                      // Scenario: \"marketing\" | \"collection\"; empty if unclear\n")
                .append("  \"overdue\": false,                  // Explicitly states overdue (collection); default false\n")
                .append("  \"loan_arrears_info\": {             // Loan/arrears info (collection)\n")
                .append("    \"principal\": null,               // Principal amount; null if unknown\n")
                .append("    \"interest_fee\": null,            // Interest/fees; null if unknown\n")
                .append("    \"total_due\": null,               // Current total due; null if unknown\n")
                .append("    \"overdue_days\": null,            // Days overdue; null if unknown\n")
                .append("    \"installment_no\": null,          // Installment/statement number; null if unknown\n")
                .append("    \"product_name\": \"\",            // Loan product name; empty if none\n")
                .append("    \"currency\": \"\",                // Currency code (e.g., \"PHP\"); empty if unclear\n")
                .append("    \"raw_amount_mentions\": []        // List of raw amount phrases found in text; [] if none\n")
                .append("  },\n")
                .append("  \"due_date\": \"\",                  // Due date: use yyyy-MM-dd if year is present; if only MM-dd is stated, use MM-dd; empty if none\n")
                .append("  \"payday\": \"\",                    // Payday mentioned by the other party; empty if none\n")
                .append("  \"delay_reason\": \"\",              // Reason for delay/non-payment (collection); empty if none\n")
                .append("  \"job_status\": {                    // Employment/income status (both marketing and collection)\n")
                .append("    \"employed\": null,                // true/false; null if unclear\n")
                .append("    \"occupation\": \"\"               // Occupation; empty if unknown\n")
                .append("  },\n")
                .append("  \"fund_source\": \"\",               // Repayment fund source (salary/borrow from friend/etc.); empty if none\n")
                .append("  \"repayment_intent\": \"\",          // Repayment intent (collection): \"promise\" | \"refuse\" | \"negotiate\" | \"unable\" | \"unsure\"; empty if unclear\n")
                .append("  \"promised_payment\": {              // Promised repayment (collection)\n")
                .append("    \"promised\": false,               // Whether a promise was made; default false\n")
                .append("    \"date\": \"\",                    // Promised date; empty if none\n")
                .append("    \"amount\": null,                  // Promised amount; null if none\n")
                .append("    \"channel\": \"\"                  // Payment channel (bank/e-wallet/OTC); empty if unclear\n")
                .append("  },\n")
                .append("  \"marketing_offer\": {               // [Marketing only] Product & offer info (keep defaults if not marketing)\n")
                .append("    \"product_name\": \"\",            // Promoted product/app; empty if none\n")
                .append("    \"max_loan_amount\": null,         // Max loanable amount; null if unknown\n")
                .append("    \"currency\": \"\",                // Currency code (e.g., \"PHP\"); empty if unclear\n")
                .append("    \"tenor_days\": null,              // Tenor in days; if only months/weeks are given, keep null and preserve raw phrase in raw_offer_mentions\n")
                .append("    \"interest_rate_apr\": null,       // APR in percent; if only other rate types are given, keep null and preserve raw phrase\n")
                .append("    \"fees\": [],                      // Fee highlights (processing/late fee/etc.); [] if none\n")
                .append("    \"eligibility\": [],               // Eligibility (ID/payslip/age/employment/etc.); [] if none\n")
                .append("    \"promo\": \"\",                   // Campaign/discount/free-interest-period/etc.; empty if none\n")
                .append("    \"application_channel\": \"\",     // Application channel (app/web/branch/hotline); empty if unclear\n")
                .append("    \"application_link\": \"\",        // Concrete link/path if mentioned; otherwise empty\n")
                .append("    \"cta_deadline\": \"\",            // Deadline if mentioned; empty if none\n")
                .append("    \"raw_offer_mentions\": []         // Raw phrases related to amount/rate/tenor/fees; [] if none\n")
                .append("  },\n")
                .append("  \"application_intent\": \"\",        // Prospect's intent for marketing: \"accept\" | \"decline\" | \"consider\" | \"info_only\"; empty if unclear\n")
                .append("  \"documents_required\": [],          // Documents to prepare (ID/payslip/etc.); [] if none\n")
                .append("  \"follow_up\": {                      // Follow-up (both marketing and collection)\n")
                .append("    \"scheduled\": false,               // Whether a follow-up/recall was scheduled; default false\n")
                .append("    \"datetime\": \"\",                 // Appointment time (same date rules); empty if none\n")
                .append("    \"channel\": \"\"                   // Follow-up channel (call/sms/whatsapp/etc.); empty if none\n")
                .append("  },\n")
                .append("  \"contact_update\": {                 // Contact info update\n")
                .append("    \"new_phone\": \"\",                // New phone number; empty if none\n")
                .append("    \"preferred_method\": \"\"          // Preferred contact method: call/sms/whatsapp/messenger/email; empty if unclear\n")
                .append("  },\n")
                .append("  \"call_frequency\": 0,                // Call frequency mentioned in this call; 0 if none\n")
                .append("  \"rejected\": false,                  // Was the call rejected/hung up; default false\n")
                .append("  \"wrong_number\": false,              // Explicit wrong number/not target; default false\n")
                .append("  \"languages\": []                     // Detected language codes (e.g., [\"fil\", \"en\"]); [] if unclear\n")
                .append("}\n\n")
                .append("Extraction rules:\n")
                .append("1) Only rely on information explicitly present in the transcript. For unknowns, use defaults (\"\", 0, false, [], null).\n")
                .append("2) Amount/Rate/Tenor:\n")
                .append("   - Remove currency symbols/letters (₱/P/PHP), spaces, and thousand separators; keep only digits and at most one decimal point.\n")
                .append("3) Dates: prefer yyyy-MM-dd; if only MM-dd is available, use MM-dd.\n")
                .append("4) Strictly return valid JSON (double quotes, lowercase booleans, correct null).\n\n")
                .append("### Transcript Start:\n")
                .append(content).append("\n")
                .append("### Transcript End.");
        return sb.toString();
    }

}
